<!--模态框组件-->
<!-- 
   传入属性：
    overlayCss: 遮罩层额外css，默认空字符串  --String
    windowCss: 内容展示窗口额外css，默认空字符串  --String
    backGroundColor: 背景颜色，默认'rgba(0,0,0,0.6)'  --String
    position: 展示位置，默认 center (上:top,右:right,下:bottom,左:left,中:center,)  --String
    animationForm: 动画展示形式，默认 auto (与背景一起开始:auto,先背景后内容:layered)  --String
    overlayAnimationTime: 遮罩层动画展示时间；单位秒，默认0.5  --Number
    windowAnimationTime: 展示窗口动画展示时间；单位秒，默认0.3  --Number
    zIndex:模态框展示层级，默认：99  --Number
    animationSwitch: 动画开关，默认true (开启动画:true,关闭动画:false)  --Boolean
    overlayClose: 遮罩点击开关，默认true (允许遮罩点击关闭窗口:true,不允许遮罩点击关闭窗口:false)  --Boolean
    contentSlideSwitch: 是否允许内部内容滑动开关，默认false (允许内部内容滑动:true,不允许内部内容滑动:false)  --Boolean

    回调函数:
     windowStateAfter:窗口改变后函数，使用父级回调函数，传递当前窗口状态 (open:模态框打开 , close:模态框关闭)
     windowStateFront:窗口改变前函数，使用父级回调函数，传递当前窗口状态 (open:模态框打开 , close:模态框关闭)
     overlayClick:使用父级回调函数，传递当前 overlayClose遮罩点击开关状态 + popupState模态框状态

    使用方法:
     1、此组件支持插槽写法，只需要将需要的内容写入标签中，即可在开启模态框时展示出来
        示例: 
            <popup>
               <div :style="{ height: 200 + 'px', width: '200px', background: '#fff' }" ></div>
            </popup>
     2、需要打开或关闭此模态框时,不用额外定义任何的变量，只需要使用ref获取到本组件的实例信息，然后调用open或close方法进打开或关闭操作
        示例: 
            <popup ref="Popup"></popup>
            this.$refs.Popup.open()
    
    注意:
     1、为了防错，在本组件的最外层的click点击 和 scroll滑动都阻止了冒泡操作，若是有其他需求，可以对其中进行修改
     2、内容展示窗口也对click事件进行了阻止冒泡操作
     3、因为考虑到大多数使用情况，并结合性能使用思考，故改变的是以css名称为主，且并非 动画 而是 transition过渡，若是对此有其他需要的请自行修改
     4、因为模态框为了正常显示所以对 openPopup开启变量 首次加载监听开启，存在只是内部修改状态，导致回调函数调用的情况，故声明此状态首次组件加载时，
        禁用所有回调函数；若是需要首次加载也需要返回数据，可以传入
  -->
<!-- ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————— -->

<template>
  <!-- 组件外层 -->
  <!-- 1、若是动画开关开启了，则会展示带有过渡属性的css，没有开启则是空 -->
  <!-- 2、overlayAnimationCss为 遮罩层动画css，会根据模态框展示开关进行对应的变化 -->
  <!-- 3、overlayControlCss为 遮罩层控制css，当动画结束 或 直接需要隐藏时会有对应css，否则是空 -->
  <!-- 4、components_popup-+ position为 根据展示状态进行赋予对应的css样式 -->
  <div
    class="components_popup"
    :class="`${animationSwitch ? 'components_popup-change' : ''} ${
      'components_popup-' + position
    } ${overlayAnimationCss} ${overlayControlCss} ${overlayCss} `"
    :style="{
      backgroundColor: backGroundColor,
      transitionDuration: `${overlayAnimationTime}s`,
      zIndex: zIndex,
    }"
    @click.stop="overlayClick"
    @scroll.stop="overlayScroll"
  >
    <!-- 内容展示窗口 -->
    <!-- 1、若是动画开关开启了，则会展示带有过渡属性的css，没有开启则是空 -->
    <!-- 2、popupWindowAnimationCss 为内容展示窗口动画css，会根据模态框展示开关进行对应的变化 -->
    <!-- 3、components_popup-window-change components_popup-window- + position为 根据展示状态进行赋予对应的css样式 -->
    <div
      class="components_popup-window"
      :class="`${
        animationSwitch
          ? 'components_popup-window-change components_popup-window-' + position
          : ''
      } ${popupWindowAnimationCss} ${windowCss}`"
      :style="{
        transitionDuration: `${windowAnimationTime}s`,
      }"
      @click.stop=""
    >
      <!-- 展示内容插入插槽 -->
      <slot></slot>
    </div>
  </div>
</template>

<script>
export default {
  // 页面or组件 命名
  name: 'components_popup',

  // 传参
  props: {
    // 遮罩层额外css，默认空字符串
    // ---------------------------------------------------------------------------------------------
    overlayCss: {
      type: [String],
      default: '',
    },

    // 内容展示窗口额外css，默认空字符串
    // ---------------------------------------------------------------------------------------------
    windowCss: {
      type: [String],
      default: '',
    },

    // 背景颜色，默认'rgba(0,0,0,0.6)'
    // ---------------------------------------------------------------------------------------------
    backGroundColor: {
      type: [String],
      default: 'rgba(0,0,0,0.6)',
    },

    // 展示位置，默认 center (上:top,右:right,下:bottom,左:left,中:center,)
    // ---------------------------------------------------------------------------------------------
    position: {
      type: [String],
      default: 'center',
    },

    // 动画展示形式，默认 auto (与背景一起开始:auto,先背景后内容:layered)
    // ---------------------------------------------------------------------------------------------
    animationForm: {
      type: [String],
      default: 'auto',
    },

    // 遮罩层动画展示时间；单位秒，默认0.5
    // ---------------------------------------------------------------------------------------------
    overlayAnimationTime: {
      type: [Number],
      default: 0.5,
    },

    // 展示窗口动画展示时间；单位秒，默认0.3
    // ---------------------------------------------------------------------------------------------
    windowAnimationTime: {
      type: [Number],
      default: 0.3,
    },

    // 模态框展示层级，默认：99
    // ---------------------------------------------------------------------------------------------
    zIndex: {
      type: [Number],
      default: 99,
    },

    // 动画开关，默认true (开启动画:true,关闭动画:false)
    // ---------------------------------------------------------------------------------------------
    animationSwitch: {
      type: [Boolean],
      default: true,
    },

    // 遮罩点击开关，默认true (允许遮罩点击关闭窗口:true,不允许遮罩点击关闭窗口:false)
    // ---------------------------------------------------------------------------------------------
    overlayClose: {
      type: [Boolean],
      default: true,
    },

    // 是否允许内部内容滑动开关，默认false (允许内部内容滑动:true,不允许内部内容滑动:false)
    // ---------------------------------------------------------------------------------------------
    contentSlideSwitch: {
      type: [Boolean],
      default: false,
    },

    // 是否允许模态框首次加载时执行回调函数，默认false（true:允许；false:不允许）
    // ---------------------------------------------------------------------------------------------
    isFirstCallback: {
      type: [Boolean],
      default: false,
    },
  },

  // 参数变量声明区域
  // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
  data() {
    return {
      firstState: true, // 模态框首次展示状态（因为模态框为了正常显示所以首次加载监听开启，可能存在bug，故声明此状态首次组件加载时，用于控制组件进行立即隐藏）
      firstCallback: this.isFirstCallback, // 同步 是否允许模态框首次加载时执行回调函数 状态
      openPopup: false, // 模态框展示开关
      overlayAnimationCss: '', //遮罩层动画css
      overlayControlCss: '', // 遮罩层控制css
      popupWindowAnimationCss: '', // 内容展示窗口动画css
    }
  },
  // 监听使用区
  // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
  watch: {
    // 监听模态框展示开关的变化，且也需监听首次改变
    openPopup: {
      handler(msg) {
        // 需要动画展示的判断操作
        // ---------------------------------------------------------------------------------------------
        // 若是模态框打开时
        if (msg) {
          //改变前函数，使用父级回调函数，传递当前窗口打开的状态 (首次状态允许时才执行)
          this.firstCallback && this.$emit('windowStateFront', 'open')

          // 清空模态框隐藏css类名
          this.overlayControlCss = ''

          // 根据是否开启遮挡判断使用遮挡函数
          this.contentSlideSwitch && this.openPageSwipe()

          // 在动画开关开启时，使用定时器赋予对应的样式，这样才能有动画效果
          this.animationSwitch &&
            setTimeout(() => {
              // 赋予遮罩层动画css对应css名称
              this.overlayAnimationCss = 'components_popup-changeShow'

              // 判断 动画展示形式 是否是依次的类型 layered
              // ====================================================================================
              if (this.animationForm === 'layered') {
                // 是的话，启动根据遮罩层动画展示时间为依据的定时器
                setTimeout(() => {
                  // 赋予内容展示窗口动画css对应的css
                  this.popupWindowAnimationCss =
                    'components_popup-changeShow-window'
                }, this.overlayAnimationTime * 1000)
              } else {
                // 若 动画展示形式 不是依次的类型 layered 则直接 赋予内容展示窗口动画css对应的css
                // ====================================================================================
                this.popupWindowAnimationCss =
                  'components_popup-changeShow-window'
              }
            })

          //改变后函数，使用父级回调函数，传递当前窗口打开的状态(首次状态允许时才执行)
          this.firstCallback && this.$emit('windowStateAfter', 'open')
        } else {
          // 模态框关闭时
          // ---------------------------------------------------------------------------------------------
          //改变前函数，使用父级回调函数，传递当前窗口关闭的状态(首次状态允许时才执行)
          this.firstCallback && this.$emit('windowStateFront', 'close')

          // 根据是否开启遮挡判断使用遮挡函数
          // ====================================================================================
          !this.contentSlideSwitch && this.closurePageSwipe()

          // 如果不需要动画时+第一次展示时防止出现bug
          // ====================================================================================
          if (!this.animationSwitch || this.firstState) {
            // 直接对遮罩层控制css进行赋值操作
            this.overlayControlCss = 'components_popup_hide'

            //改变后函数，使用父级回调函数，传递当前窗口关闭的状态(首次状态允许时才执行)
            this.firstCallback && this.$emit('windowStateAfter', 'close')

            // 放开首次加载时的回调函数禁止
            !this.firstCallback && (this.firstCallback = true)

            // 当首次加载状态开启时，将其关闭
            this.firstState && (this.firstState = false)

            // 弹出逻辑，不需要执行后续判断操作
            return
          }

          // 清空 内容展示窗口动画css
          this.popupWindowAnimationCss = ''
          // 延迟时间变量
          let delayTimes = 0

          // 判断 动画展示形式 是否是依次的类型 layered
          // ====================================================================================
          if (this.animationForm === 'layered') {
            // 是的话，根据内容展示窗口动画时间进行定时
            setTimeout(() => {
              // 清空遮罩层动画css
              this.overlayAnimationCss = ''
            }, this.windowAnimationTime * 1000)

            // 给延迟时间变量赋予值 即模态框+内容展示框动画时间总和
            delayTimes = this.overlayAnimationTime + this.windowAnimationTime
          } else {
            // 若 动画展示形式 不是依次的类型 layered 则直接 清空遮罩层动画css
            // ====================================================================================
            this.overlayAnimationCss = ''

            // 不是延迟动画模式的情况下，延迟时间变量为遮罩层动画展示时间
            delayTimes = this.overlayAnimationTime
          }

          // 使用定时器触发，标记弹窗彻底隐藏的css
          // ====================================================================================
          setTimeout(() => {
            this.overlayControlCss = 'components_popup_hide'

            //改变后函数，使用父级回调函数，传递当前窗口关闭的状态(首次状态允许时才执行)
            this.firstCallback && this.$emit('windowStateAfter', 'close')
          }, delayTimes * 1000)
        }

        // 放开首次加载时的回调函数禁止
        !this.firstCallback && (this.firstCallback = true)

        // 当首次加载状态开启时，将其关闭
        this.firstState && (this.firstState = false)
      },
      immediate: true,
    },
  },

  // 无内存函数使用区域
  // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
  methods: {
    // 模态框开启函数
    open() {
      this.openPopup = true
    },

    // 模态框关闭函数
    // ---------------------------------------------------------------------------------------------
    close() {
      this.openPopup = false
    },

    // 遮罩层滑动函数
    // ---------------------------------------------------------------------------------------------
    overlayScroll(e) {
      // 防止滑动影响，再次禁用模态框自身的滑动冒泡功能
      e.stopPropagation()
    },

    // 遮罩层点击函数
    // ---------------------------------------------------------------------------------------------
    overlayClick() {
      // 使用父级回调函数，传递当前 overlayClose遮罩点击开关状态 + popupState模态框状态
      this.$emit('overlayClick', {
        overlayClose: this.overlayClose,
        popupState: this.openPopup,
      })

      // 只有当 遮罩点击开关 开启时，才能关闭本模态框
      this.overlayClose && this.close()
    },

    // 开启遮挡页面禁止滑动函数
    // ---------------------------------------------------------------------------------------------
    openPageSwipe() {
      // 给html标签绑定高度+让其不能滑动
      document.documentElement.style.height = '100%'
      document.documentElement.style.overflow = 'hidden'
      // 修改body的高度+让其不能进行滑动
      document.body.style.height = '100%'
      document.body.style.overflow = 'hidden'
    },

    // 关闭遮挡页面禁止滑动函数
    // ---------------------------------------------------------------------------------------------
    closurePageSwipe() {
      // 给html标签绑定高度+让其不能滑动
      document.documentElement.style.height = ''
      document.documentElement.style.overflow = ''
      // 修改body的高度+让其不能进行滑动
      document.body.style.height = ''
      document.body.style.overflow = ''
    },
  },
}
</script>

<style scoped>
/* 模态框组件 */
.components_popup {
  width: 100%;
  height: 100%;
  overflow: scroll;
  display: flex;
  position: fixed;
  top: 0;
  left: 0;
  opacity: 1;
}

/* 模态框组件+内容展示窗口 需要动画时样式 */
.components_popup-change,
.components_popup-window-change {
  transition-property: all;
}
/* 模态框组件需要动画时样式  */
.components_popup-change {
  opacity: 0;
}

/* 恢复展示样式 */
.components_popup-changeShow {
  opacity: 1;
}

/* 全局整体样式隐藏 */
.components_popup_hide {
  display: none;
}

/* 模态框组件不同位置展示css */
/* ==================================================================================== */
/* 从上呈现时 */
.components_popup-top {
  align-items: flex-start;
  justify-content: center;
}

/* 从左呈现时 */
.components_popup-left {
  align-items: center;
  justify-content: flex-start;
}

/* 从下呈现时 */
.components_popup-bottom {
  align-items: flex-end;
  justify-content: center;
}

/* 从右呈现时 */
.components_popup-right {
  align-items: center;
  justify-content: flex-end;
}

/* 居中呈现时 */
.components_popup-center {
  align-items: center;
  justify-content: center;
}

/* 内容展示窗口 */
/* --------------------------------------------------------------------------------------------- */
.components_popup-window {
  display: inline-block;
  position: relative;
}

/* 需要动画时内容展示窗口不同位置展示css */
/* ==================================================================================== */
/* 从上呈现时 */
.components_popup-window-top {
  transform: translate(0, -100%);
}

/* 从左呈现时 */
.components_popup-window-left {
  transform: translate(-100%, 0);
}

/* 从下呈现时 */
.components_popup-window-bottom {
  transform: translate(0, 100%);
}

/* 从右呈现时 */
.components_popup-window-right {
  transform: translate(100%, 0);
}

/* 居中呈现时 */
.components_popup-window-center {
  opacity: 0;
}

/* 恢复展示样式 */
.components_popup-changeShow-window {
  transform: translate(0, 0);
  opacity: 1;
}
</style>
